package iterators;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import config.GlobalConfig;
import controlFlowGraph.CFG;
import controlFlowGraph.CFGEdge;
import controlFlowGraph.CFGNode;
import domains.AnalysisDomain;

public class RecursiveIterator extends FixpointIterator {

	private Set<CFGNode> stable;
	private HashMap<CFGNode,Set<CFGNode>> neededForComputation;
	private HashMap<CFGNode,Set<CFGNode>> dependencies;
	
	@Override
	public void doIteration(CFG cfg) {
		stable = new HashSet<CFGNode>();
		neededForComputation = new HashMap<CFGNode,Set<CFGNode>>();
		dependencies = new HashMap<CFGNode,Set<CFGNode>>();

		for(CFGNode c : cfg.getAllNodes()){
			neededForComputation.put(c,computeNeeds(c,cfg));
			dependencies.put(c,new HashSet<CFGNode>());
		}
		
		for(CFGNode c: cfg.getAllNodes()){
			solve(c);
		}
	}
	
	private void solve(CFGNode node){
		if(!stable.contains(node)){
			stable.add(node);
			Set<CFGNode> needed = neededForComputation.get(node);
			for(CFGNode n:needed){
				eval(node,n);
			}
			AnalysisDomain newVal = recompute(node);
			if(!newVal.equals(node.getAnalysisInfo())){
				Set<CFGNode> change = dependencies.get(node);
				dependencies.put(node, new HashSet<CFGNode>());
				node.setAnalysisInfo(newVal);
				stable.removeAll(change);
				for(CFGNode next: change){
					solve(next);
				}
			}
		}
	}
	
	private Set<CFGNode> computeNeeds(CFGNode node, CFG cfg){
		Set<CFGNode> necessary = new HashSet<CFGNode>();
		if(GlobalConfig.isForward()){
			List<CFGEdge> in = node.getInEdges();
			for(CFGEdge e: in){
				necessary.add(e.getFromNode());
			}
		} else if(GlobalConfig.isBackward()){
			List<CFGEdge> out = node.getOutEdges();
			for(CFGEdge e: out){
				necessary.add(e.getToNode());
			}
		} else {
			System.out.println("Analysis Type not supported!");
		}
		return necessary;
	}
	
	private void eval(CFGNode node1, CFGNode node2){
		solve(node2);
		
		Set<CFGNode> newDependent = new HashSet<CFGNode>();
		newDependent.addAll(dependencies.get(node2));
		newDependent.add(node1);
		dependencies.put(node2, newDependent);
		
		// TODO: do I need to return the newly computed result for node2?
	}
}
